package com.springcloud.gateway.config;

import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springcloud.auth.config.OauthConfig;
import com.springcloud.auth.service.TTokenService;
import com.springcloud.auth.utils.OauthUtils;
import com.springcloud.auth.utils.TokenEntity;
import com.springcloud.auth.utils.TokenHelper;
import com.springcloud.auth.utils.TokenUtil;
import com.springcloud.commom.api.R;
import com.springcloud.commom.constant.CommonConstant;
import com.springcloud.commom.utils.RedisUtil;
import com.springcloud.gateway.provider.ResponseProvider;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.springcloud.auth.utils.OauthUtils.ACCESS_TOKEN;

/**
 * 自定义一个全局过滤器
 * 实现 globalfilter , ordered接口
 */
@Component
@Slf4j
@AllArgsConstructor
public class LoginFilter implements GlobalFilter, Ordered {

    private ObjectMapper objectMapper;

    @Resource
    private RedisUtil redis;

    @Resource
    private TTokenService tokenService;

    /**
     * 执行过滤器中的业务逻辑
     * 对请求参数中的access-token进行判断
     * 如果存在此参数:代表已经认证成功
     * 如果不存在此参数 : 认证失败.
     * ServerWebExchange : 相当于请求和响应的上下文(zuul中的RequestContext)
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("============执行了自定义的全局过滤器============");
        //1.获取请求参数Authorization
        List<String> head = exchange.getRequest().getHeaders().get("Authorization");

        String token = "";
        if (!CollectionUtils.isEmpty(head)) {
            token = head.get(0);

            if (StrUtil.isEmpty(token)) {
                //2.如果不存在 : 认证失败
                log.info("============缺失令牌,鉴权失败===========");
                ServerHttpResponse resp = exchange.getResponse();
                return response(HttpStatus.UNAUTHORIZED, resp, "缺失令牌,鉴权失败"); //请求结束
            } else {
                token = token.replace("Bearer ", "");
            }
            log.info("============token==========[{}]", token);
        }

        String requestUrl = exchange.getRequest().getPath().value();
        log.info("============requestUrl:" + requestUrl);

        List<String> list = Arrays.stream(CommonConstant.EXCLUDE_URLS).collect(Collectors.toList());

        boolean flag = false;
        for (String str : list) {
            if (requestUrl.contains(str)) {
                flag = true;

                break;
            }
        }

        //3.排除不需要校验的接口
        if (!flag) {
            //4.调用鉴权服务
            R<String> result = auth(requestUrl, token);
            if (result.getCode() != 200) {
                //鉴权失败
                ServerHttpResponse resp = exchange.getResponse();

                return response(HttpStatus.INTERNAL_SERVER_ERROR, resp, result.getMsg());
            }
        }

        //5.如果存在,继续执行

        //经过网关添加网关信息
        byte[] token11 = Base64Utils.encode("keys".getBytes());
        String[] headerValues = {new String(token11)};
        ServerHttpRequest build = exchange.getRequest().mutate().header("gateway", headerValues).build();

        //设置token
        build.mutate().header(ACCESS_TOKEN, token);

        ServerWebExchange newExchange = exchange.mutate().request(build).build();
        return chain.filter(newExchange);
    }

    /**
     * 指定过滤器的执行顺序 , 返回值越小,执行优先级越高
     */
    @Override
    public int getOrder() {
        return 0;
    }

    private Mono<Void> response(HttpStatus status, ServerHttpResponse resp, String msg) {
        resp.setStatusCode(status);
        resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        String result = "";
        try {
            result = objectMapper.writeValueAsString(ResponseProvider.response(HttpStatus.UNAUTHORIZED.value(), msg));
        } catch (JsonProcessingException e) {
            log.error(e.getMessage(), e);
        }
        DataBuffer buffer = resp.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
        return resp.writeWith(Flux.just(buffer));
    }

    public R<String> auth(String url, String accessToken) {
        //校验权限
        try {
            List<String> list = Arrays.stream(CommonConstant.EXCLUDE_URLS).collect(Collectors.toList());

            boolean flag = false;
            for (String str : list) {
                if (url.contains(str)) {
                    flag = true;

                    break;
                }
            }
            //若为排除接口则放开
            if (flag) {
                return R.success();
            }

            log.info("================校验token==============");
            if (StringUtils.isBlank(accessToken)) {
                //解析是否为图片链接
                //若是静态资源，则放开
                if (url.contains(".jpg") || url.contains(".png") || url.contains(".jpeg")) {
                    return R.success();
                }

                log.info("================token 为null==============");
                return R.fail("accessToken is empty!");
            }

            String token = redis.get(OauthConfig.AUTH_ACCESS_TOKEN + accessToken);
            if (org.apache.commons.lang3.StringUtils.isBlank(token)) {
                // 若还是拿不到,说明accessToken已经过期
                return R.fail("accessToken is expire!");
            }

            log.info("redis中拿到token!");

            // 解析Token
            TokenEntity tokenEntity = TokenHelper.validateToken(token);
            if (tokenEntity.getValid()) {
                log.info("=============拿到令牌!=============");
                Map<String, String> tokenMap = tokenEntity.getPayload();
                String acToken = tokenMap.get(OauthConfig.TOKEN_ACCESS_TOKEN);
                if (!acToken.equals(accessToken)) {
                    // 客户端与服务端的token 匹配
                    return R.fail("accessToken is error!");
                }
                // 验证通过，保存 当前用户
                TokenUtil.setUserInfo(tokenMap);

                //存入redis
                String refreshToken = TokenUtil.getReToken(tokenMap);

                OauthUtils.saveRedisToken(accessToken, refreshToken, token);

                //刷新数据库的token失效时间
                tokenService.updateRefreshTime(accessToken);

                return R.success();
            } else {
                return R.fail("accessToken is error!");
            }

        } catch (Exception e) {
            e.printStackTrace();

            return R.fail("error!");
        }
    }
}
